/* eslint-disable max-statements */
import type { MDXComponents } from 'mdx/types';
import type { GetStaticPaths, GetStaticProps } from 'next';
import dynamic from 'next/dynamic';
import Head from 'next/head';
import NextImage, { type ImageProps as NextImageProps } from 'next/image';
import { useRouter } from 'next/router';
import Script from 'next/script';
import type { ComponentType, HTMLAttributes } from 'react';
import { useIntl } from 'react-intl';
import {
Code,
Gallery,
getLayout,
Link,
Overview,
PageLayout,
Sharing,
SocialLink,
Spinner,
Heading,
List,
ListItem,
Figure,
type MetaItemData,
type MetaValues,
Time,
} from '../../components';
import styles from '../../styles/pages/project.module.scss';
import type { NextPageWithLayout, ProjectPreview, Repos } from '../../types';
import { ROUTES } from '../../utils/constants';
import {
getSchemaJson,
getSinglePageSchema,
getWebPageSchema,
} from '../../utils/helpers';
import {
getProjectData,
getProjectFilenames,
loadTranslation,
type Messages,
} from '../../utils/helpers/server';
import { useBreadcrumb, useGithubApi, useSettings } from '../../utils/hooks';
const BorderedImage = (props: NextImageProps) => (
);
const H1 = ({
children = '',
...props
}: HTMLAttributes) => (
{children}
);
const H2 = ({
children = '',
...props
}: HTMLAttributes) => (
{children}
);
const H3 = ({
children = '',
...props
}: HTMLAttributes) => (
{children}
);
const H4 = ({
children = '',
...props
}: HTMLAttributes) => (
{children}
);
const H5 = ({
children = '',
...props
}: HTMLAttributes) => (
{children}
);
const H6 = ({
children = '',
...props
}: HTMLAttributes) => (
{children}
);
const OrderedList = ({
children,
...props
}: HTMLAttributes) => (
{children}
);
const UnorderedList = ({
children,
...props
}: HTMLAttributes) => (
{children}
);
const components: MDXComponents = {
Code,
Gallery,
h1: H1,
h2: H2,
h3: H3,
h4: H4,
h5: H5,
h6: H6,
Image: BorderedImage,
li: ListItem,
Link,
ol: OrderedList,
ul: UnorderedList,
};
type ProjectPageProps = {
project: ProjectPreview;
translation: Messages;
};
/**
* Project page.
*/
const ProjectPage: NextPageWithLayout = ({ project }) => {
const { id, intro, meta, title } = project;
const { cover, dates, license, repos, seo, technologies } = meta;
const intl = useIntl();
const { items: breadcrumbItems, schema: breadcrumbSchema } = useBreadcrumb({
title,
url: `${ROUTES.PROJECTS}/${id}`,
});
const ProjectContent: ComponentType = dynamic(
async () => import(`../../content/projects/${id}.mdx`),
{
loading: () => ,
}
);
const { website } = useSettings();
const { asPath } = useRouter();
const page = {
title: `${seo.title} - ${website.name}`,
url: `${website.url}${asPath}`,
};
const headerMeta: (MetaItemData | undefined)[] = [
{
id: 'publication-date',
label: intl.formatMessage({
defaultMessage: 'Published on:',
description: 'ProjectsPage: publication date label',
id: 'HxZvY4',
}),
value: ,
},
dates.update && dates.update !== dates.publication
? {
id: 'update-date',
label: intl.formatMessage({
defaultMessage: 'Updated on:',
description: 'ProjectsPage: update date label',
id: 'wQrvgw',
}),
value: ,
}
: undefined,
];
const filteredHeaderMeta = headerMeta.filter(
(item): item is MetaItemData => !!item
);
/**
* Retrieve the repositories links.
*
* @param {Repos} repositories - A repositories object.
* @returns {MetaValues[]} - An array of meta values.
*/
const getReposLinks = (repositories: Repos): MetaValues[] => {
const links: MetaValues[] = [];
const githubLabel = intl.formatMessage({
defaultMessage: 'Github profile',
description: 'ProjectsPage: Github profile link',
id: 'Nx8Jo5',
});
const gitlabLabel = intl.formatMessage({
defaultMessage: 'Gitlab profile',
description: 'ProjectsPage: Gitlab profile link',
id: 'sECHDg',
});
if (repositories.github)
links.push({
id: 'github',
value: (
),
});
if (repositories.gitlab)
links.push({
id: 'gitlab',
value: (
),
});
return links;
};
const loadingRepoPopularity = intl.formatMessage({
defaultMessage: 'Loading the repository popularity...',
description: 'ProjectsPage: loading repository popularity',
id: 'RwI3B9',
});
const { isError, isLoading, data } = useGithubApi(
/*
* Repo should be defined for each project so for now it is safe for my
* use-case. However, I should refactored it to handle cases where it is
* not defined. The logic should be extracted in another component I think.
*
* TODO: fix this hardly readable argument
*/
meta.repos ? meta.repos.github ?? '' : ''
);
if (isError) return 'Error';
if (isLoading || !data) return ;
const getRepoPopularity = (repo: string) => {
const stars = intl.formatMessage(
{
defaultMessage:
'{starsCount, plural, =0 {No stars on Github} one {# star on Github} other {# stars on Github}}',
description: 'ProjectsPage: Github stars count',
id: 'sI7gJK',
},
{ starsCount: data.stargazers_count }
);
const popularityUrl = `https://github.com/${repo}/stargazers`;
return (
<>
⭐
{stars}
>
);
};
const overviewMeta: (MetaItemData | undefined)[] = [
{
id: 'creation-date',
label: intl.formatMessage({
defaultMessage: 'Created on:',
description: 'ProjectsPage: creation date label',
id: 'wVFA4m',
}),
value: ,
},
{
id: 'update-date',
label: intl.formatMessage({
defaultMessage: 'Updated on:',
description: 'ProjectsPage: update date label',
id: 'wQrvgw',
}),
value: ,
},
license
? {
id: 'license',
label: intl.formatMessage({
defaultMessage: 'License:',
description: 'ProjectsPage: license label',
id: 'VtYzuv',
}),
value: license,
}
: undefined,
repos?.github
? {
id: 'popularity',
label: intl.formatMessage({
defaultMessage: 'Popularity:',
description: 'ProjectsPage: popularity label',
id: 'KrNvQi',
}),
value: getRepoPopularity(repos.github),
}
: undefined,
repos
? {
id: 'repositories',
label: intl.formatMessage({
defaultMessage: 'Repositories:',
description: 'ProjectsPage: repositories label',
id: 'iDIKb7',
}),
value: getReposLinks(repos),
}
: undefined,
technologies
? {
id: 'technologies',
label: intl.formatMessage({
defaultMessage: 'Technologies:',
description: 'ProjectsPage: technologies label',
id: 'RwNZ6p',
}),
value: technologies.map((techno) => {
return { id: techno, value: techno };
}),
}
: undefined,
];
const filteredOverviewMeta = overviewMeta.filter(
(item): item is MetaItemData => !!item
);
const webpageSchema = getWebPageSchema({
description: seo.description,
locale: website.locales.default,
slug: asPath,
title: seo.title,
updateDate: dates.update,
});
const articleSchema = getSinglePageSchema({
cover: `/projects/${id}.jpg`,
dates,
description: intro,
id: 'project',
kind: 'page',
locale: website.locales.default,
slug: asPath,
title,
});
const schemaJsonLd = getSchemaJson([webpageSchema, articleSchema]);
return (
<>
{page.title}
{/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */}
{/*eslint-disable-next-line react/jsx-no-literals -- Content allowed */}
,
]}
>
>
);
};
ProjectPage.getLayout = (page) =>
getLayout(page, { useGrid: true, withExtraPadding: true });
export const getStaticProps: GetStaticProps = async ({
locale,
params,
}) => {
const translation = await loadTranslation(locale);
const project = await getProjectData(params ? (params.slug as string) : '');
return {
props: {
project,
translation,
},
};
};
export const getStaticPaths: GetStaticPaths = () => {
const filenames = getProjectFilenames();
const paths = filenames.map((filename) => {
return {
params: {
slug: filename,
},
};
});
return {
paths,
fallback: false,
};
};
export default ProjectPage;